/*=============================================================

    fancy.cpp  -  Copyright (C) 1993-2008 by Don Cross
    http://cosinekitty.com/chenard


    Completely rewriting ParseFancyMove to accept only
    either PGN or longmove notation (e.g., "e2e4", "e7e8q").
    I am losing interest in supporting goofy non-standard 
    notations for chess moves!

=============================================================*/

#include <string.h>

#include "chess.h"

bool ParseFancyMove (
    const char  *string,
    ChessBoard  &board,
    int         &source,
    int         &dest,
    SQUARE      &promIndex,
    Move        &move )         // redundant with (source,dest,promIndex), but may be more immediately useful
{
    // initialize all output parameters:
    promIndex = 0;      // not a promotion (yet)

    if (string == NULL) {
        ChessFatal ("ParseFancyMove(NULL)");
    }

    if (string[0] >= 'a' && string[0] <= 'h' &&
        string[1] >= '1' && string[1] <= '8' &&
        string[2] >= 'a' && string[2] <= 'h' &&
        string[3] >= '1' && string[3] <= '8' ) 
    {
        source = OFFSET (string[0] - 'a' + 2, string[1] - '1' + 2);
        dest   = OFFSET (string[2] - 'a' + 2, string[3] - '1' + 2);

        if (string[4] == '\0') {
            return true;    // Not pawn promotion, but well-formed longmove.  Caller must check legality!
        } else {
            switch (string[4]) {
                case 'q':
                    promIndex = Q_INDEX;
                    return true;

                case 'r':
                    promIndex = R_INDEX;
                    return true;

                case 'b':
                    promIndex = B_INDEX;
                    return true;

                case 'n':
                    promIndex = N_INDEX;
                    return true;

                default:
                    // This is not a well-formed longmove, but it could be PGN... though I doubt it.  
                    // Keep trying to scan with the PGN algorithm below, just in case.
                    break;   
            }
        }
    }

    size_t  stringLength = strlen (string);
    if (stringLength < 2 || stringLength > 7) {
        return false;   // the length of this string is incompatible with any possible PGN formatted move!
    }

    // http://www.very-best.de/pgn-spec.htm  Section 8.2.3 includes the following:
    //
    //    "Neither the appearance nor the absence of either a check or checkmating indicator 
    //     is used for disambiguation purposes. This means that if two (or more) pieces of the 
    //     same type can move to the same square the differences in checking status of the moves 
    //     does not allieviate the need for the standard rank and file disabiguation described above."
    //
    // To be a little bit on the tolerant side, we will match moves whether or not 'string' has a '+' or '#' on the end,
    // since this cannot possibly lead to ambiguity in move notation.
    // To implement this parser tolerance, we will strip off '+' or '#' whenever it appears on either
    // the input value 'string', or in the generated local variable 'pgn'.

    char    copy [8];           // we just checked length of 'string', so...
    strcpy (copy, string);      // ... this is perfectly safe!
    switch (copy [stringLength - 1]) {
        case '+':
        case '#':
            copy [stringLength - 1] = '\0';
            break;
    }

    char    pgn [MAX_MOVE_STRLEN + 1];
    int     numMatchingMoves = 0;

    MoveList ml;
    board.GenMoves (ml);
    for (int i=0; i < ml.num; ++i) {
        // Convert move to PGN...
        FormatChessMove (board, ml, ml.m[i], pgn);
        // We assume the PGN generator (FormatChessMove) is working correctly,
        // meaning that it will not generate the same PGN notation for two distinct chess moves.

        size_t pgnLength = strlen(pgn);
        if (pgnLength >= 2 && pgnLength <= 7) {
            switch (pgn [pgnLength - 1]) {
                case '+':
                case '#':
                    pgn [pgnLength - 1] = '\0';
                    break;
            }
        } else {
            ChessFatal ("Invalid PGN length in move parser.");
        }

        if (0 == strcmp (copy, pgn)) {
            SQUARE piece = ml.m[i].actualOffsets (board, source, dest);
            if (piece != EMPTY) {
                promIndex = UPIECE_INDEX (piece);
            }

            if (++numMatchingMoves != 1) {
                ChessFatal ("PGN ambiguity in move parser.");
            }

            move = ml.m[i];
        }
    }

    if (numMatchingMoves == 1) {
        return true;
    } else {
        // I have seen cases where a PGN file generated by other software
        // will have a move like "Ngf3", even though "Nf3" was unambiguous.
        // Let's try to hack around that here by checking for that case and using recursion.
        // Note that in a case like "Na1b3", we may recurse twice:  "Na1b3" -> "N1b3" -> "Nb3".
        // If deleting the second character generates a truly ambiguous move, it will never match
        // any PGN string we generate above in the comparison loop, so we will always return
        // false after either 1 or 2 recursions.
        if (numMatchingMoves == 0) {    // didn't match anything at all, even disregarding '+' or '#' at end
            if (stringLength >= 4) {    // long enough to contain an unnecessary source rank/file/square disambiguator
                if (string[0]=='N' || string[0]=='B' || string[0]=='R' || string[0]=='Q' || string[0]=='K') {     // not moving a pawn
                    if ((string[1] >= 'a' && string[1] <= 'h') || (string[1] >= '1' && string[1] <= '8')) {     // rank or file in second character?
                        char shorterString[8];                      // we made sure string had a length in the range 2..7 above
                        shorterString[0] = string[0];               // keep first character
                        strcpy (&shorterString[1], &string[2]);     // skip over second character but keep rest of string
                        return ParseFancyMove (shorterString, board, source, dest, promIndex, move);
                    }
                }
            }
        }

        // If the caller is ill-behaved and ignores the return value, 
        // at least give them output parameters that don't match any legal move.
        // This is especially important when numMatchingMoves > 1,
        // because we have already set the output parameters to the most recent matching move
        // (assuming ChessFatal has not yet ended execution: it can behave differently on different platforms).
        source    = 0;
        dest      = 0;
        promIndex = 0;
        memset (&move, 0, sizeof(Move));
        return false;
    }
}

